package grpcsvc

import (
	"context"
	"encoding/base32"
	"encoding/binary"
	"fmt"
	pb "generatorserver/internal/protobuf"
	"generatorserver/module/otp"
	"hash/crc32"
	"publiclib/grpcutil"
	"time"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

const (
	Company = "Lexar"
)

// server is used to implement OTPServer.
type OTPServer struct {
	pb.UnimplementedOTPServer
}

func (s *OTPServer) ComputeCode(_ context.Context, req *pb.OTPComputeCodeReq) (*pb.OTPComputeCodeResp, error) {
	log.Infof("Received req: %+v", req)
	if len(req.Secret) == 0 {
		return nil, grpcutil.Error(grpcutil.InvalidArgument, "Secret length is 0.")
	}
	_now := time.Now().Unix()
	t0 := _now / 30
	t1 := 30 - (_now % 30)
	num := otp.ComputeCode(req.Secret, t0)
	return &pb.OTPComputeCodeResp{
		OTP: fmt.Sprintf("%.6d", num),
		TTL: int32(t1),
	}, nil
}

func (s *OTPServer) Authenticate(_ context.Context, req *pb.OTPAuthenticateReq) (*pb.OTPAuthenticateResp, error) {
	log.Infof("Received req: %+v", req)
	if len(req.Secret) == 0 {
		return nil, grpcutil.Error(grpcutil.InvalidArgument, "Secret length is 0.")
	}
	if len(req.Code) == 0 {
		return nil, grpcutil.Error(grpcutil.InvalidArgument, "Code length is 0.")
	}
	oc := &otp.OTPConfig{
		Secret:     req.Secret,
		WindowSize: 17,
	}
	ok, err := oc.Authenticate(req.Code)
	if err != nil {
		return nil, err
	}
	return &pb.OTPAuthenticateResp{
		Succeeded: ok,
	}, nil
}

func (s *OTPServer) Generator(_ context.Context, req *pb.OTPGeneratorReq) (*pb.OTPGeneratorResp, error) {
	log.Infof("Received req: %+v", req)
	uid := uuid.Must(uuid.NewRandom())
	bid := [16]byte(uid)
	src := make([]byte, 20)
	copy(src, bid[:])
	c32 := crc32.ChecksumIEEE(bid[:])
	binary.BigEndian.PutUint32(src[16:], c32)
	if len(req.UserEmail) == 0 {
		return nil, grpcutil.Error(grpcutil.InvalidArgument, "UserEmail length is 0.")
	}
	secret := base32.StdEncoding.EncodeToString(src)
	oc := &otp.OTPConfig{
		Secret: secret,
	}
	return &pb.OTPGeneratorResp{
		Secret: base32.StdEncoding.EncodeToString(src),
		URI:    oc.ProvisionURIWithIssuer(req.UserEmail, Company),
	}, nil
}
